<!--
 * @Description: antv x6封装组件
-->
<template>
  <div class="_ehl_drag_graph">
    <div v-if="tool.show" class="_ehl_drag_tools">
      <div
        v-if="tool.delete"
        class="_ehl_drag_del"
        @click="
          changeLineWithId('ac6ecd40-2517-4370-a6ee-c1c0335e0e88', {
            connector: {
              name: 'rounded',
              args: { radius: 10 },
            },
            attrs: {
              line: {
                targetMarker: 'classic',
                stroke: '#f5222d',
              },
            },
          })
        "
        >更改指定线的样式</div
      >
      <div
        v-if="tool.delete"
        class="_ehl_drag_del"
        @click="
          changeLineStyle({
            connector: { name: 'smooth' },
            attrs: {
              line: {
                stroke: '#faad14',
                targetMarker: 'classic',
              },
            },
          })
        "
        >更改所有选中线的样式</div
      >
      <div v-if="tool.add" class="_ehl_drag_del" @click="addNodeTest()">增加节点</div>
      <div v-if="tool.delete" class="_ehl_drag_del" @click="delNodeOrLine">删除</div>
      <div v-if="tool.redo" class="_ehl_drag_del" @click="onRedo">重做</div>
      <div v-if="tool.undo" class="_ehl_drag_del" @click="onUndo">撤销</div>
      <div v-if="tool.format" class="_ehl_drag_format" @click="formatNode">格式化</div>
      <div v-if="tool.export" class="_ehl_drag_format" @click="getJSON">导出</div>
      <ehl-select
        v-model="edge.value"
        :style="{ width: '150px' }"
        :options="edge.options"
        @change="val => changeEdgeStyle(val.value)"
      />
    </div>
    <div class="_ehl_drag_main" id="container"></div>
    <div class="_ehl_drag_nodata" v-if="nodeList.length === 0">
      <div class="nodataImg"></div>
      <span class="nodataTip">当前暂无节点图</span>
    </div>
    <div class="_ehl_drag_nodata" v-if="disable"></div>
    <slot />
  </div>
</template>
<script>
  import Vue from 'vue'
  import { Graph } from '@antv/x6'
  import { DagreLayout } from '@antv/layout'
  import '@antv/x6-vue-shape'
  import vueNode from './ehlDragNode.vue'
  import { inClipboard } from '@/common/utils/api.js'
  export default {
    name:'ehlDragGraph',
    props: {
      //显示哪些工具
      tool: {
        type: Object,
        default: () => ({
          show: true,
          add: true,
          delete: true,
          redo: true,
          undo: true,
          format: true,
          export: true,
          edgeStyle: true,
        }),
      },
      //数据源，包含nodes和edges
      value: {
        type: Array,
        default: () => [],
      },
      //是否增加线上标签信息
      lineMessage: {
        type: Boolean,
        default: () => false,
      },
      //是否禁用
      disable: {
        type: Boolean,
        default: () => false,
      },
      //线的样式选项
      edgeOption: {
        type: Array,
        default: () => [
          {
            label: '实线',
            value: {
              connector: {
                name: 'normal',
              },
              attrs: {
                line: {
                  targetMarker: 'classic',
                  stroke: '#007AFF',
                },
              },
            },
          },
          {
            label: '贝塞尔曲线',
            value: {
              connector: { name: 'smooth' },
              attrs: {
                line: {
                  stroke: '#faad14',
                  targetMarker: 'classic',
                },
              },
            },
          },
          {
            label: '虚线',
            value: {
              connector: { name: 'smooth' },
              attrs: {
                line: {
                  stroke: '#1890ff',
                  strokeDasharray: 5,
                  targetMarker: 'classic',
                  style: {
                    animation: 'ant-line 30s infinite linear',
                  },
                },
              },
            },
          },
        ],
      },
      //配置背景网格
      grid: {
        type: Object,
        default: () => ({
          size: 10,
          visible: true, //绘制网格
          type: 'doubleMesh',
          args: [
            {
              color: '#eee', //主网格线颜色
              thickness: 1, //主网格线宽度
            },
            {
              color: '#ddd', //次网格线颜色
              thickness: 1, //次网格线宽度
              factor: 4, //主次网格线间隔
            },
          ],
        }),
      },
      //配置链接桩-一个整体
      // portAttr: {
      //   type: Object,
      //   default: () => ({
      //     center: {
      //       markup: [
      //         {
      //           tagName: 'rect',
      //           selector: 'rect',
      //         },
      //       ],
      //       position: {
      //         name: 'absolute',
      //         args: { x: -20, y: -20 },
      //       },
      //       zIndex: -1,
      //       attrs: {
      //         rect: {
      //           magnet: true,
      //           stroke: 'transparent',
      //           fill: 'transparent',
      //           strokeWidth: 1,
      //           width: 140,
      //           height: 140,
      //         },
      //       },
      //     },
      //   }),
      // },
      //配置链接桩-多个
      portAttr: {
        type: Object,
        default: () => {
          let tmp = {
            // markup: [
            //   {
            //     tagName: 'circle',
            //     selector: 'circle',
            //   },
            // ],
            // zIndex: 10,
            attrs: {
              circle: {
                r: 5,
                magnet: true,
                // stroke: '#31d0c6',
                stroke: '#ffffff',
                strokeWidth: 1,
                fill: '#007aff',
              },
            },
          }
          return {
            num: 1, //每条边上几个桩
            attr: {
              left: Object.assign({}, tmp, {
                position: {
                  name: 'left',
                },
              }),
              right: Object.assign({}, tmp, {
                position: {
                  name: 'right',
                },
              }),
              // top: Object.assign({}, tmp, {
              //   position: {
              //     name: 'top',
              //   },
              // }),
              // bottom: Object.assign({}, tmp, {
              //   position: {
              //     name: 'bottom',
              //   },
              // }),
            },
          }
        },
      },
      //配置布局格式
      dagreLayout: {
        type: Object,
        default: () => ({
          type: 'dagre',
          rankdir: 'TB',
          align: 'DL', //UL
          ranksep: 30,
          nodesep: 15,
          controlPoints: true,
        }),
      },
      //验证连线是否合法
      validateEdge: {
        type: Function,
        default: (edge, edges) => {
          return true
        },
      },
      //用于接收其他form的配置
      otherParam: { type: Object, default: () => ({}) },
    },
    components: { ehlSelect: () => $import('ehlSelect') },
    data() {
      return {
        graph: null, //图的实例
        nodeList: [], //点集合
        lineList: [], //线集合
        history: null, //图的实例
        oldCellAndEdge: '',
        edge: {
          value: {},
          options: this.edgeOption,
        },
        //图的相关配置
        config: {
          autoResize: true,
          grid: this.grid, //配置背景网格
          panning: true, //画布是否可以拖动
          history: true, //开启撤销重做
          selecting: {
            enabled: false,
            showNodeSelectionBox: true, //显示节点的选择框
            showEdgeSelectionBox: true, //显示边的选择框
            rubberband: true, //启用框选
            modifiers: 'ctrl', //多选快捷键
            movable: true, //选中的节点是否一起移动
          }, //点选/框选
          mousewheel: {
            enabled: true,
            modifiers: 'alt',
          },
          snapline: true, //对齐线
          highlighting: {},
          connecting: {
            // snap: {
            //   //自动吸附
            //   radius: 10,
            // },
            allowMulti: true, //起始节点和终止之间创建多条边
            allowBlank: false, //连接到画布空白位置
            allowLoop: true, //创建循环连线
            allowNode: false, //允许边链接到节点
            allowPort: true, //允许边链接到链接桩
            allowEdge: false, //边链接到另一个边
            highlight: true, //高亮显示所有可用的连接桩
            createEdge() {
              return this.createEdge({
                // router: {
                //   name: 'manhattan',
                //   args: {
                //     startDirections: ['top', 'bottom', 'left', 'right'],
                //     endDirections: ['top', 'bottom', 'left', 'right'],
                //   },
                // },
                ...this.edgeAttr,
              })
            },
            validateEdge({ edge, type, previous }) {
              //在这判断哪些连线非法，返回false
              return this.validateEdge(
                edge.getProp(),
                this.getEdges().map(i => {
                  return i.getProp()
                }),
              )
            },
          },
        },
      }
    },
    created() {
      //注册vue组件
      Vue.component(vueNode.name, vueNode)
      this.otherParam.vue = this.$parent
      // Graph.registerVueComponent(this.vueNode.name, this.vueNode, true)
    },
    mounted() {
      this.initGraph()
      this.refresh({ format: true })
    },
    methods: {
      //直接赋值不用调用，延迟赋值需要调用
      refresh(data) {
        this.initNodeAndEdge()
        if (data && data.format == true) {
          this.formatNode()
        } else {
          this.reDrawNode()
        }
        if (data && data.center == true) {
          this.setCenter()
        }
      },
      //格式化节点
      formatNode() {
        //布局实例
        let model = new DagreLayout(this.dagreLayout).layout({
          nodes: this.nodeList,
          edges: this.lineList,
        })
        this.graph && model && this.reDrawNode(model)
      },
      //按照最新的nodeList和lineList或者提供的model绘图
      reDrawNode(model) {
        if (model) {
          //一开始格式化才会调用
          this.graph.fromJSON(model)
        } else {
          //后期更新数据使用，不会再格式化了
          let json = JSON.stringify({ cells: [...this.nodeList, ...this.lineList] })
          if (json !== this.oldCellAndEdge) {
            console.log(this.graph)
            this.graph.fromJSON({ cells: [...this.nodeList, ...this.lineList] })
            this.oldCellAndEdge = JSON.stringify({
              cells: [...this.nodeList, ...this.lineList],
            })
          }
        }
      },
      //更新指定节点信息
      upDateAppointNode(id, creatNewNode) {
        let del = this.graph.removeNode(id, { silent: true })
        del = creatNewNode(del)
        this.graph.addNode(del, { silent: true })
      },
      //设置图居中
      setCenter() {
        this.graph.centerContent()
      },
      //设置点居中
      setCenterPoint(x, y) {
        this.graph.centerPoint(x, y)
      },
      /***********************************************************/
      //参数必须是对象
      addParam(object) {
        Object.assign(this.otherParam, object || {})
      },
      //生成通用参数
      generateParam(param = {}) {
        return Object.assign(
          {},
          { current: param },
          {
            graph: this,
            nodeList: this.nodeList,
            lineList: this.lineList,
          },
          { other: this.otherParam || {} },
        )
      },
      //初始化组件
      initGraph() {
        if (this.lineMessage) {
          this.config = this.initEdgeMessage(this.config)
        }
        //初始化画布
        this.graph = new Graph(
          Object.assign({ container: document.getElementById('container') }, this.config),
        )
        //获取外部的vue实例
        this.graph.vue = this.otherParam
        this.graph.edgeAttr = this.edgeOption[0].value
        this.graph.validateEdge = this.validateEdge
        //记录历史
        this.history = this.graph.history
        //增加监听事件
        // this.graph.on('node:click', param => {
        //   let vData = ''
        //   for (let index = 0; index < param.e.target.attributes.length; index++) {
        //     const element = param.e.target.attributes[index]
        //     if (element.nodeName === 'data-v-cus') {
        //       vData = element.nodeValue
        //     }
        //   }
        //   debugger
        //   // console.log('attributes', param.e.target.attributes)
        //   this.$emit(
        //     'nodeClick',
        //     this.generateParam({
        //       param,
        //       targetClass: param.e.target.classList.value,
        //       offsetX: param.e.clientX,
        //       offsetY: param.e.clientY,
        //       vData: vData,
        //       node: param.node.getProp(),
        //       nodes: this.graph.getNodes().map(i => {
        //         return i.getProp()
        //       }),
        //     }),
        //   )
        // })
        this.graph.on('node:moved', param => {
          param.node.store.data.param.event = 'move'
        })
        this.graph.on('edge:click', param => {
          this.$emit(
            'edgeClick',
            this.generateParam({
              param,
              node: param.edge.getProp(),
              nodes: this.graph.getEdges().map(i => {
                return i.getProp()
              }),
            }),
          )
        })
        //增删改后同步数据
        this.graph.on('cell:added', param => {
          this.$emit('input', this.graph.toJSON().cells)
          setTimeout(() => {
            this.initNodeAndEdge()
          }, 100)
        })
        this.graph.on('cell:changed', param => {
          this.$emit('input', this.graph.toJSON().cells)
          setTimeout(() => {
            this.initNodeAndEdge()
          }, 100)
        })
        this.graph.on('cell:removed', param => {
          this.$emit('input', this.graph.toJSON().cells)
          setTimeout(() => {
            this.initNodeAndEdge()
          }, 100)
        })
      },
      //html形式增加线上信息
      initEdgeMessage(config) {
        /*lineList上需要增加如下属性
        import { Markup } from '@antv/x6'
        defaultLabel: {
          markup: Markup.getForeignObjectMarkup(),
          attrs: {
            fo: {
              width: 20,
              height: 30,
              x: 20,
              y: -15,
            },
          },
        },
        label: {
          position: 0,
          text: link[i].scriptName,
        },
        */
        config.onEdgeLabelRendered = args => {
          const { selectors } = args
          const content = selectors.foContent
          if (content) {
            const btn = document.createElement('button')
            btn.appendChild(document.createTextNode('I'))
            btn.style.width = '100%'
            btn.style.height = '100%'
            btn.style.borderRadius = '4px'
            btn.style.textAlign = 'center'
            btn.style.color = '#000'
            btn.style.cursor = 'pointer'
            btn.title = args.label.text
            btn.style.background = '#ffd591'
            btn.style.border = '1px solid #ffa940'
            btn.addEventListener('click', () => {
              inClipboard(args.label.text)
            })
            content.appendChild(btn)
          }
        }
        return config
      },
      //初始化点和线
      initNodeAndEdge() {
        let _this = this
        let cells =
          this.value && this.value.length > 0 ? this.value : this.graph.toJSON().cells
        this.nodeList = []
        this.lineList = []
        cells.forEach(i => {
          if (i.shape !== 'edge') {
            _this.nodeList.push(i)
          } else {
            _this.lineList.push(i)
          }
        })
      },
      //增加节点
      addNodeTest() {
        this.nodata = false
        this.graph.addNode({
          shape: 'vue-shape',
          x: 50,
          y: 50,
          param: { name: 1111 },
          width: 100,
          height: 40,
          ports: {
            groups: {
              left: {
                attrs: {
                  circle: {
                    r: 5,
                    magnet: true,
                    stroke: '#ffffff',
                    strokeWidth: 1,
                    fill: '#007aff',
                  },
                },
                position: {
                  name: 'left',
                },
              },
              right: {
                attrs: {
                  circle: {
                    r: 5,
                    magnet: true,
                    stroke: '#ffffff',
                    strokeWidth: 1,
                    fill: '#0077dd',
                  },
                },
                position: {
                  name: 'right',
                },
              },
            },
            items: [
              {
                id: 0,
                group: 'left',
              },
              {
                id: 1,
                group: 'right',
              },
            ],
          },
          component: {
            template: `<drag-node></drag-node>`,
          },
        })
      },
      //增加节点
      addNode(node) {
        // let pos = this.$refs.container.getBoundingClientRect()
        // let scale = this.graph.zoom()//scale为视图缩放倍数
        // let x = node.x - pos.left
        // let y = node.y - pos.top
        this.nodata = false
        this.graph.addNode(node)
      },
      //删除节点或者线
      delNodeOrLine() {
        let tmp = this.graph.getSelectedCells()
        tmp.forEach(i => {
          i.remove()
        })
      },
      //更改指定线的样式
      changeLineWithId(id, style) {
        this.lineList.forEach(i => {
          if (i.id === id) {
            if (style.connector) {
              i.connector = style.connector
            }
            if (style.attrs) {
              i.attrs = style.attrs
            }
          }
        })
        this.reDrawNode()
      },
      //更改所有选中线的样式
      changeLineStyle(style) {
        let tmp = this.graph.getSelectedCells()
        tmp.forEach(i => {
          let edge = i.getProp()
          if (style.connector) {
            edge.connector = style.connector
          }
          if (style.attrs) {
            edge.attrs = style.attrs
          }
        })
        this.initNodeAndEdge()
        this.reDrawNode()
      },
      //撤销操作
      onUndo() {
        this.history.undo()
      },
      //重做操作
      onRedo() {
        this.history.redo()
      },
      //导出图的数据
      getJSON() {
        console.log(this.graph.toJSON())
        return this.graph.toJSON()
      },
      //更改线的样式
      changeEdgeStyle(attr, node) {
        if (node && attr.attrs) {
          node.setAttrs(attr.attrs)
        } else {
          this.graph.edgeAttr = attr
        }
      },
      //显示隐藏链接桩
      showPorts(ports, show) {
        for (let i = 0, len = ports.length; i < len; i = i + 1) {
          ports[i].style.visibility = show ? 'visible' : 'hidden'
        }
      },
      //根据节点寻找一条线上的所有，返回节点数组
      findByNode(node) {},
      //根据线寻找两边的节点，返回节点数组
      findByLine(line) {},
    },
  }
</script>
<style lang="less" scoped>
  ._ehl_drag_graph {
    position: relative;

    ._ehl_drag_tools {
      position: absolute;
      top: 0;
      right: 0;
      z-index: 9;
      display: flex;
      align-items: center;

      div {
        margin: 0 10px;
        cursor: pointer;
      }
    }

    /deep/ ._ehl_drag_main {
      width: 100% !important;
      height: 100% !important;

      body {
        min-width: auto;
      }

      .x6-widget-selection-box {
        border: 2px dotted blue;
      }
    }

    ._ehl_drag_nodata {
      position: absolute;
      top: 0;
      right: 0;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      width: 100%;
      height: 100%;
      font-size: 30px;

      .nodataImg {
        width: 226px;
        height: 112px;
        margin-bottom: 20px;
        background: url('~@/common/assets/nodata.png') no-repeat;
      }

      .nodataTip {
        font-size: 18px;
        color: #a3aec1;
      }
    }
  }
</style>
